using System;
using System.Collections.Generic;
using System.Linq;

namespace Glue.Internals.Verification
{
    internal class Randomizers
    {
        private readonly Dictionary<Type,Func<object>> randomizers = new Dictionary<Type, Func<object>>();
        private readonly Random random = new Random(Environment.TickCount);
        private readonly EnumRandomizer enumRandomizer;

        public Randomizers()
        {
            enumRandomizer = new EnumRandomizer(random);
            randomizers.Add(typeof(bool),()=>new RandomCollection<bool>(new []{true,false}, random).GetRandomValue());
            randomizers.Add(typeof(bool?), () => new RandomCollection<bool?>(new bool?[] { true, false, null },random).GetRandomValue());
            randomizers.Add(typeof(byte), () => Convert.ToByte(random.Next(0,256)));
            randomizers.Add(typeof(byte?), () => Convert.ToByte(random.Next(0, 256)));
            randomizers.Add(typeof(sbyte), () => Convert.ToSByte(random.Next(-128, 128)));
            randomizers.Add(typeof(sbyte?), () => Convert.ToSByte(random.Next(-128, 128)));
            randomizers.Add(typeof(char), () => Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65))));
            randomizers.Add(typeof(char?), () => Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65))));
            randomizers.Add(typeof(decimal), () => random.NextDecimal());
            randomizers.Add(typeof(decimal?), () => random.NextDecimal());
            randomizers.Add(typeof(double), () => random.NextDouble());
            randomizers.Add(typeof(double?), () => random.NextDouble());
            randomizers.Add(typeof(float), () => Convert.ToSingle(random.NextDouble()));
            randomizers.Add(typeof(float?), () => Convert.ToSingle(random.NextDouble()));
            randomizers.Add(typeof(int), () => random.NextInt32());
            randomizers.Add(typeof(int?), () => random.NextInt32());
            randomizers.Add(typeof(uint), () => (uint)random.Next(0,int.MaxValue));
            randomizers.Add(typeof(uint?), () => (uint?)random.Next(0, int.MaxValue));
            randomizers.Add(typeof(long), () => (long)((random.NextDouble() * 2.0 - 1.0) * long.MaxValue));
            randomizers.Add(typeof(long?), () => (long?)((random.NextDouble() * 2.0 - 1.0) * long.MaxValue));
            randomizers.Add(typeof(ulong), () => (ulong)((Math.Abs(random.NextDouble()) * 2.0 - 1.0) * long.MaxValue));
            randomizers.Add(typeof(ulong?), () => (ulong?)((Math.Abs(random.NextDouble()) * 2.0 - 1.0) * long.MaxValue));
            randomizers.Add(typeof(short), () => (short)random.Next(-32768,32768));
            randomizers.Add(typeof(short?), () => (short?)random.Next(-32768, 32768));
            randomizers.Add(typeof(ushort), () => (ushort)random.Next(0, 65536));
            randomizers.Add(typeof(ushort?), () => (ushort?)random.Next(0, 65536));
            randomizers.Add(typeof(String), () => Guid.NewGuid().ToString());
            randomizers.Add(typeof(Guid), () => Guid.NewGuid());
            randomizers.Add(typeof (DateTime),
                            () => new DateTime(random.Next(1, 2020), random.Next(1, 13), random.Next(1, 29)));
            randomizers.Add(typeof(DateTime?),
                            () => new DateTime(random.Next(1, 2020), random.Next(1, 13), random.Next(1, 29)));
        }

        public object GetRandomValue(Type type)
        {
            if (randomizers.ContainsKey(type))
                return randomizers[type]();
            if (type.IsEnum)
                return enumRandomizer.Randomize(type);

            throw new GlueException("Cannot randomize value of type '" + type.GetReadableName() + "'");
        }

        public void AddRandomizer(Type type, Func<object> randomizer)
        {
            if (randomizers.ContainsKey(type))
                randomizers.Remove(type);
            randomizers.Add(type,randomizer);

        }

        public bool Contains(Type type)
        {
            return randomizers.ContainsKey(type);
        }

        private class RandomCollection<T>
        {
            private readonly IList<T> collection;
            private readonly Random random;

            public RandomCollection(IList<T> collection, Random random)
            {
                this.collection = collection;
                this.random = random;
            }

            public object GetRandomValue()
            {
                var position = random.Next(collection.Count());
                return collection[position];
            }
        }
    }
}